#ifndef	__platform_H__
#define	__platform_H__

////////////////////////////////////////////////////////////////////////////////////////////////////
// useful derivations and augmentations
////////////////////////////////////////////////////////////////////////////////////////////////////

// REND_TARGET_ISA
#define	__TARGET_ISA_POWERPC		1
#define	__TARGET_ISA_INTEL			2
#define	__TARGET_ISA_ARM			3
#define	__TARGET_ISA_X86_64			4

// REND_TARGET_ENDIANNESS
#define	__TARGET_ENDIANNESS_BIG		1
#define	__TARGET_ENDIANNESS_LITTLE	2

// REND_TARGET_OS
#define	__TARGET_OS_OSX				1
#define	__TARGET_OS_LINUX			2

#if		defined(__GNUC__)

	#if		defined(__ppc__) || defined(__PPC__) || defined(__powerpc__) || defined(__POWERPC__)

		#define	REND_TARGET_ISA			__TARGET_ISA_POWERPC
		#define	REND_TARGET_ENDIANNESS	__TARGET_ENDIANNESS_BIG

	#elif	defined(__i386__) || defined(__INTEL__)

		#define	REND_TARGET_ISA			__TARGET_ISA_INTEL
		#define REND_TARGET_ENDIANNESS	__TARGET_ENDIANNESS_LITTLE

	#elif	__x86_64__ == 1 || __amd64__ == 1

		#define	REND_TARGET_ISA			__TARGET_ISA_X86_64
		#define REND_TARGET_ENDIANNESS	__TARGET_ENDIANNESS_LITTLE

	#elif	defined(__arm__)

		#define	REND_TARGET_ISA			__TARGET_ISA_ARM
		#define REND_TARGET_ENDIANNESS	__TARGET_ENDIANNESS_LITTLE

	#endif

#endif

#if		defined(__APPLE__) && defined(__MACH__)

	#define	REND_TARGET_OS	__TARGET_OS_OSX

#else

	#define	REND_TARGET_OS	__TARGET_OS_LINUX

#endif

#if		!defined(DEBUG) && !defined(_DEBUG)

	#if !defined(NDEBUG)
	#define NDEBUG
	#endif

#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// platform-specific includes
////////////////////////////////////////////////////////////////////////////////////////////////////

#if		REND_TARGET_OS == __TARGET_OS_OSX

	#include <assert.h>
	#include <math.h>
	#include <stdio.h>

	#include <CoreServices/CoreServices.h>
	#include <mach/mach_time.h>

#else

	#include <assert.h>
	#include <math.h>
	#include <stdio.h>
	#include <unistd.h>
	#include <time.h>

	#include <EGL/egl.h>
	#include <GLES2/gl2.h>

#endif

////////////////////////////////////////////////////////////////////////////////////////////////////
// check on some gnuc math facilities
////////////////////////////////////////////////////////////////////////////////////////////////////

#if		defined(__GNUC__)

	#if _GLIBCXX_USE_C99_MATH
	#warning _GLIBCXX_USE_C99_MATH is true
	#endif

	#if _GLIBCXX_USE_C99_FP_MACROS_DYNAMIC
	#warning _GLIBCXX_USE_C99_FP_MACROS_DYNAMIC is true
	#endif

	#ifdef __USE_ISOC99
	#warning __USE_ISOC99 is defined
	#endif

	#ifdef fpclassify
	#warning fpclassify is defined
	#endif

	#ifdef isnormal
	#warning isnormal is defined
	#endif

	#ifdef isfinite
	#warning isfinite is defined
	#endif

	#ifdef isnan
	#warning isnan is defined
	#endif

	#ifdef signbit
	#warning signbit is defined
	#endif

#endif


namespace rend
{

////////////////////////////////////////////////////////////////////////////////////////////////////
// bit-counted scalars
////////////////////////////////////////////////////////////////////////////////////////////////////

typedef signed char		int8;
typedef unsigned char	uint8;

typedef short			int16;
typedef unsigned short	uint16;

typedef int				int32;
typedef unsigned int	uint32;

////////////////////////////////////////////////////////////////////////////////////////////////////
// local namespace fill-ins for GL types not always defined in ES implementations
////////////////////////////////////////////////////////////////////////////////////////////////////

typedef char             GLchar;

////////////////////////////////////////////////////////////////////////////////////////////////////
// float helpers
////////////////////////////////////////////////////////////////////////////////////////////////////

enum
{
	IEEE754_SIGN_MASK = 0x80000000U
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// float-to-int conversion using 'truncate' rounding mode
//		- x,	float	: fp value,		input
// returns
//		long int
inline long int toIntTrunc(float x)
{
	return static_cast< long int >(x);
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// float-to-int conversion using 'nearest' rounding mode
//		- x,	float	: fp value,		input
// returns
//		long int
// note
//		- __INTEL__
//		we pop the fp stack by default, so the compiler may decide to preserve the stack top by inserting
//		an "fld %st(0)" immediately before us if the passed argument is an object used later in the context.
//		particularly this may happen when the argument is a non-temporary object (i.e. a declared variable).
//		for example:
//
//		{
//			float a, b;
//			...
//			long int c = ToIntRound(a + b);	// "a + b" is a temporary object not used further, dropping it off the stack is ok
//			long int d = ToIntRound(a);		// "a" is a non-temporary object not referred any further; dropping it is still ok
//			...								// do something with "c" & "d" here
//		}
//
//		{
//			float a, b;
//			...
//			long int c = ToIntRound(a);		// "a" gets used later in "a + b", so we get an "fld %st(0)" penalty here
//			long int d = ToIntRound(a + b);	// "a + b" is a temporary object not used any further and can be discard freely
//			...								// do something with "c" & "d" here
//		}
#if		REND_TARGET_ISA == __TARGET_ISA_INTEL

inline long int toIntRound(float x)
{
	long int ix;

	__asm__ __volatile__ ("fistpl %0" : "=m" (ix) : "t" (x) : "cc", "st");

	return ix;
}

#else

inline long int toIntRound(float x)
{
	return lrintf(x);
}

#endif


enum FloatPrecision
{
	PRECISION_SINGLE,
	PRECISION_DOUBLE,
	PRECISION_MAXIMAL
};

#if		REND_TARGET_ISA == __TARGET_ISA_INTEL

inline void setFloatPrecision(FloatPrecision prec)
{
	enum INTEL_FPUCW_PRECISION
	{
		INTEL_FPUCW_PRECISION_MASK		= 0x0300,

		INTEL_FPUCW_PRECISION_24		= 0x0000,
		INTEL_FPUCW_PRECISION_RESERVED	= 0x0100,
		INTEL_FPUCW_PRECISION_53		= 0x0200,
		INTEL_FPUCW_PRECISION_64		= 0x0300,

		INTEL_FPUCW_PRECISION_DEFAULT	= INTEL_FPUCW_PRECISION_53
	};

	INTEL_FPUCW_PRECISION cw_prec = INTEL_FPUCW_PRECISION_DEFAULT;

	switch (prec)
	{
	case PRECISION_SINGLE:

		cw_prec = INTEL_FPUCW_PRECISION_24;
		break;

	case PRECISION_DOUBLE:

		cw_prec = INTEL_FPUCW_PRECISION_53;
		break;

	case PRECISION_MAXIMAL:

		cw_prec = INTEL_FPUCW_PRECISION_64;
		break;
	}

	unsigned short int cw;

	__asm__ __volatile__ ("fnstcw %0" : "=m" (cw));

	cw = (cw & ~INTEL_FPUCW_PRECISION_MASK) | (cw_prec & INTEL_FPUCW_PRECISION_MASK);

	__asm__ __volatile__ ("fldcw %0" : : "m" (cw));
}

#else 

inline void setFloatPrecision(FloatPrecision)
{
	// ppc and arm do not have a precision state per se - their fp ops carry their precision encoded
}

#endif

////////////////////////////////////////////////////////////////////////////////
// endianness helpers
////////////////////////////////////////////////////////////////////////////////

template < typename SCALAR_T >
inline void swapEndianness(SCALAR_T& var)
{
	assert((sizeof(var) & 1) == 0);

	int8 byte[sizeof(var)];
	int8 (&varByte)[sizeof(var)] = *reinterpret_cast< int8(*)[sizeof(var)] >(&var);

	for (unsigned i = 0; i < sizeof(var) / 2; ++i)
	{
		const unsigned j = sizeof(var) - (i + 1);
		byte[i] = varByte[j];
		byte[j] = varByte[i];
	}

	var = *reinterpret_cast< SCALAR_T* >(&byte);
}


template < typename SCALAR_T >
inline void swapEndiannessIfHostBig(SCALAR_T& var)
{
#if		REND_TARGET_ENDIANNESS ==	__TARGET_ENDIANNESS_BIG

	swapEndianness(var);

#endif
}


template < typename SCALAR_T >
inline void swapEndiannessIfHostLittle(SCALAR_T& var)
{
#if		REND_TARGET_ENDIANNESS ==	__TARGET_ENDIANNESS_LITTLE

	swapEndianness(var);

#endif
}

////////////////////////////////////////////////////////////////////////////////
// gl helpers
////////////////////////////////////////////////////////////////////////////////

inline bool reportGLError(FILE* f = stderr)
{
	const GLenum error = glGetError();

	switch (error) {

	case GL_NO_ERROR:
		return false;
		break;

	case GL_INVALID_ENUM:
		fprintf(f, "gl error: GL_INVALID_ENUM\n");
		break;

	case GL_INVALID_FRAMEBUFFER_OPERATION:
		fprintf(f, "gl error: GL_INVALID_FRAMEBUFFER_OPERATION\n");
		break;

	case GL_INVALID_VALUE:
		fprintf(f, "gl error: GL_INVALID_VALUE\n");
		break;

	case GL_INVALID_OPERATION:
		fprintf(f, "gl error: GL_INVALID_OPERATION\n");
		break;

	case GL_OUT_OF_MEMORY:
		fprintf(f, "gl error: GL_OUT_OF_MEMORY\n");
		break;

	default:
		fprintf(f, "gl error: unknown error\n");
		break;
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////
// timing
////////////////////////////////////////////////////////////////////////////////

#if		REND_TARGET_OS == __TARGET_OS_OSX

	inline unsigned long long int timer()
	{
		return mach_absolute_time();
	}

#else

	inline unsigned long long int timer()
	{
		timespec t;
		clock_gettime(CLOCK_MONOTONIC, &t);

		return t.tv_sec * (long long int) 1000000000 + t.tv_nsec; // report in the smallest viable quantum for this timer type - nanoseconds
	}

#endif


#if		REND_TARGET_OS == __TARGET_OS_OSX

	inline double timer_freq()
	{
		mach_timebase_info_data_t tb_info;
		mach_timebase_info(&tb_info);

		return double(tb_info.denom) / double(tb_info.numer) * 1e9; // mach_timebase's numerator and denominator are nanosecond-skewed
	}

#else

	inline double timer_freq()
	{
		// here we are interested in the timer quantum in Hz, not in the timer precision. since the quantum
		// of our clock_gettime()-based timer is known in advance (it's nanosecond), just report that here

		return 1e9;
	}

#endif

} // namespace rend

#endif // __platform_H__
